package btree;

import java.io.DataOutputStream;
import java.io.IOException;
import exceptions.*;
import global.PageId;
import global.RID;
import heap.HFPage;
import diskmgr.Page;

public class BTLeafPage extends BTSortedPage {

	public BTLeafPage(Page arg0, int arg1) throws IOException {
		super(arg0, arg1);
		setType(NodeType.LEAF);
	}

	public BTLeafPage(int arg0) throws ConstructPageException, IOException {
		super(arg0);
		setType(NodeType.LEAF);

	}

	public BTLeafPage(PageId pid, int arg0) throws ConstructPageException,
			IOException {
		super(pid, arg0);
		setType(NodeType.LEAF);

	}

	public KeyDataEntry split(KeyClass key, RID rid, PageId currentLeafPageId)
			throws ConstructPageException, IOException, UnpinPageException,
			IteratorException, LeafInsertRecException, DeleteRecException,
			KeyNotMatchException {
		BTLeafPage newLeafPage;
		PageId newLeafPageId;
		int keyType = BTreeFile.headerPage.getKeyType();
//		PageId currentLeafPageId = this.getCurPage();
		newLeafPage = new BTLeafPage(keyType);
		newLeafPageId = newLeafPage.getCurPage();
		
		newLeafPage.setNextPage(this.getNextPage());
		newLeafPage.setPrevPage(currentLeafPageId); // for dbl-linked list
		this.setNextPage(newLeafPageId);
		PageId rightPageId;
		rightPageId = newLeafPage.getNextPage();
		if (rightPageId.pid != INVALID_PAGE) {
			BTLeafPage rightPage;
			rightPage = new BTLeafPage(rightPageId, keyType);

			rightPage.setPrevPage(newLeafPageId);
			BTreeFile.unpinPage(rightPageId);

			// ASSERTIONS:
			// - newLeafPage, newLeafPageId valid and pinned
			// - currentLeafPage, currentLeafPageId valid and pinned
		}
		DataOutputStream trace = BTreeFile.trace;
		BTreeHeaderPage headerPage = BTreeFile.headerPage;
		if (trace != null) {
			if (headerPage.getRoot().pid != currentLeafPageId.pid)
				trace.writeBytes("SPLIT node " + currentLeafPageId
						+ " IN nodes " + currentLeafPageId + " "
						+ newLeafPageId + BTreeFile.lineSep);
			else
				trace.writeBytes("ROOTSPLIT IN nodes " + currentLeafPageId
						+ " " + newLeafPageId + BTreeFile.lineSep);
			trace.flush();
		}
		KeyDataEntry tmpEntry;
		RID firstRid = new RID();

		for (tmpEntry = this.getFirst(firstRid); tmpEntry != null; tmpEntry = this
				.getFirst(firstRid)) {

			newLeafPage.insertRecord(tmpEntry.key,
					((LeafData) (tmpEntry.data)).getData());
			if (trace != null) {
				trace.writeBytes("PUTIN node " + newLeafPageId + BTreeFile.lineSep);
				trace.flush();
			}
			this.deleteSortedRecord(firstRid);

		}

		// ASSERTIONS:
		// - currentLeafPage empty
		// - newLeafPage holds all former records from currentLeafPage

		KeyDataEntry undoEntry = null;
		for (tmpEntry = newLeafPage.getFirst(firstRid); newLeafPage
				.available_space() < this.available_space(); tmpEntry = newLeafPage
				.getFirst(firstRid)) {
			undoEntry = tmpEntry;
			this.insertRecord(tmpEntry.key,
					((LeafData) tmpEntry.data).getData());
			newLeafPage.deleteSortedRecord(firstRid);
		}

		if (BT.keyCompare(key, undoEntry.key) < 0) {
			// undo the final record
			if (this.available_space() < newLeafPage.available_space()) {
				newLeafPage.insertRecord(undoEntry.key,
						((LeafData) undoEntry.data).getData());

				this.deleteSortedRecord(new RID(this.getCurPage(), (int) this
						.getSlotCnt() - 1));
			}
		}

		// check whether <key, rid>
		// will be inserted
		// on the newly allocated or on the old leaf page

		if (BT.keyCompare(key, undoEntry.key) >= 0) {
			// the new data entry belongs on the new Leaf page
			newLeafPage.insertRecord(key, rid);

		} else {
			this.insertRecord(key, rid);
		}
		BTreeFile.unpinPage(currentLeafPageId);
//		if (trace != null) {
//			trace_children(currentLeafPageId);
//			trace_children(newLeafPageId);
//		}
		tmpEntry = newLeafPage.getFirst(firstRid);
		KeyDataEntry entry = new KeyDataEntry(tmpEntry.key, newLeafPageId);
		BTreeFile.unpinPage(newLeafPageId);
		
		return entry;
	}

	public KeyDataEntry split(KeyClass data, RID rid) {
		try {
			BTLeafPage next = new BTLeafPage(this.keyType);
			BTLeafPage oldNext = new BTLeafPage(this.getNextPage(),
					this.keyType);
			next.setNextPage(oldNext.getCurPage());
			next.setPrevPage(this.getCurPage());
			this.setNextPage(next.getCurPage());
			oldNext.setPrevPage(next.getCurPage());
			next.insertRecord(data, rid);
			int numberOfRecords = numberOfRecords();
			KeyDataEntry currentEntry = null;
			for (int i = numberOfRecords - 1; i >= numberOfRecords / 2; i--) {
				RID startRecored = new RID(this.getCurPage(), i);
				currentEntry = getCurrent(startRecored);
				this.delEntry(currentEntry);
				next.insertRecord(currentEntry.key, startRecored);
			}
			return currentEntry;
		} catch (ConstructPageException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (LeafInsertRecException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IteratorException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (LeafDeleteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	public RID insertRecord(KeyClass key, RID dataRid)
			throws LeafInsertRecException {
		try {
			KeyDataEntry newData = new KeyDataEntry(key, dataRid);

			RID record = insertRecord(newData);
			return record;
		} catch (Exception e) {

			throw new LeafInsertRecException(e, "insert record failed");

		}
	}

	public KeyDataEntry getFirst(RID rid) throws IteratorException {

		KeyDataEntry entry;

		try {
			rid.pageNo = getCurPage();
			rid.slotNo = 0; // begin with first slot

			if (getSlotCnt() <= 0) {
				return null;
			}

			entry = BT.getEntryFromBytes(getpage(), getSlotOffset(0),
					getSlotLength(0), keyType, NodeType.LEAF);

			return entry;
		} catch (Exception e) {
			throw new IteratorException(e, "Get first entry failed");
		}
	} // end of getFirst

	public KeyDataEntry getNext(RID rid) throws IteratorException {

		KeyDataEntry entry;

		try {
			rid.pageNo = getCurPage();
			rid.slotNo = rid.slotNo + 1;
			if (rid.slotNo >= getSlotCnt()) {
				return null;
			}
			entry = BT.getEntryFromBytes(getpage(), getSlotOffset(rid.slotNo),
					getSlotLength(rid.slotNo), keyType, NodeType.LEAF);

			return entry;
		} catch (Exception e) {
			throw new IteratorException(e, "Get next entry failed");
		}

	}

	public KeyDataEntry getCurrent(RID rid) throws IteratorException {
		KeyDataEntry entry;

		try {
			rid.pageNo = getCurPage();

			if (rid.slotNo >= getSlotCnt()) {
				return null;
			}
			entry = BT.getEntryFromBytes(getpage(), getSlotOffset(rid.slotNo),
					getSlotLength(rid.slotNo), keyType, NodeType.LEAF);

			return entry;
		} catch (Exception e) {
			throw new IteratorException(e, "Get current failed");
		}

	}

	/**
	 * delete a data entry in the leaf page.
	 * 
	 * @param dEntry
	 *            the entry will be deleted in the leaf page. Input parameter.
	 * @return true if deleted; false if no dEntry in the page
	 * @exception LeafDeleteException
	 *                error when delete
	 */

	public boolean delEntry(KeyDataEntry dEntry) throws LeafDeleteException {
		KeyDataEntry entry;
		RID rid = new RID();

		try {
			for (entry = getFirst(rid); entry != null; entry = getNext(rid)) {
				if (entry.equals(dEntry)) {
					if (super.deleteSortedRecord(rid) == false)
						throw new LeafDeleteException(null,
								"Delete record failed");
					return true;
				}

			}
			return false;
		} catch (Exception e) {
			throw new LeafDeleteException(e, "delete entry failed");
		}

	} // end of delEntry

}
